package cn.ava.ymcc.service.impl;

import cn.ava.ymcc.constants.CommonConstants;
import cn.ava.ymcc.domain.Course;
import cn.ava.ymcc.domain.CourseMarket;
import cn.ava.ymcc.domain.KillActivity;
import cn.ava.ymcc.domain.KillCourse;
import cn.ava.ymcc.dto.PreKillOrder;
import cn.ava.ymcc.dto.KillCourseOrderParamDto;
import cn.ava.ymcc.feign.CourseFeignClient;
import cn.ava.ymcc.mapper.KillCourseMapper;
import cn.ava.ymcc.result.JSONResult;
import cn.ava.ymcc.service.IKillActivityService;
import cn.ava.ymcc.service.IKillCourseService;
import cn.ava.ymcc.utils.AssertUtil;
import cn.ava.ymcc.utils.CodeGenerateUtils;
import cn.ava.ymcc.vo.Course2OrderVo;
import cn.ava.ymcc.vo.Courseinfo2OrderinfoVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author ava
 * @since 2022-08-23
 */
@Service
@Slf4j
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {

    @Autowired
    private IKillActivityService killActivityService;

    @Autowired
    private CourseFeignClient courseFeignClient;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 基于普通课程添加秒杀课程
     * @param killCourse
     */
    @Override
    public void addCourseKill(KillCourse killCourse) {
        // JSR303参数校验
        // 业务校验-秒杀课程: 查询出普通课程,校验状态必须为已上线
        JSONResult jsonResult = courseFeignClient.get(killCourse.getCourseId());
        AssertUtil.isTrue(jsonResult.isSuccess(),jsonResult.getMessage());
        Course course = JSONObject.parseObject(JSONObject.toJSONString(jsonResult.getData()), Course.class);
        boolean isOnline = CommonConstants.CourseStateConstant.STATE_ONLINE.equals(course.getStatus());
        AssertUtil.isTrue(isOnline,"课程状态异常!");
        // 发布的秒杀课程只能存在一个,进行校验
        KillCourse killCourseTmp = selectByCourseIdAndActivityId(killCourse.getCourseId(),killCourse.getActivityId());
        // 必须为NUll
        AssertUtil.isNull(killCourseTmp,"秒杀课程已经存在啦!");
        // 业务校验-秒杀活动: 活动必须存在且状态为已发布
        KillActivity killActivity = killActivityService.selectById(killCourse.getActivityId());
        AssertUtil.isNotNull(killActivity,"秒杀活动不存在!");
        boolean isPublish = CommonConstants.KillCourseState.STATE_WAIT_PUBLISH.equals(killActivity.getPublishStatus());
        AssertUtil.isTrue(isPublish,"活动状态异常!");
        // 添加秒杀课程
        killCourse.setPublishStatus(killActivity.getPublishStatus());
        killCourse.setStartTime(killActivity.getBeginTime());
        killCourse.setEndTime(killActivity.getEndTime());
        killCourse.setTimeStr(killActivity.getTimeStr());
        killCourse.setKillLimit(CommonConstants.KillCourseState.KILL_COUNT_ONE);
        killCourse.setCreateTime(new Date());
        insert(killCourse);
    }

    /**
     *  从redis查询出已发布的所有秒杀商品
     */
    @Override
    public List<KillCourse> onlineAll() {
        List killCourse = new ArrayList<>();
        redisTemplate.keys("activity:*").forEach(key -> killCourse.addAll(redisTemplate.opsForHash().values(key)));
        return killCourse;
    }

    /**
     * 查询秒杀商品详情
     * @param killId
     * @param activityId
     * @return
     */
    @Override
    public KillCourse onlineOne(Long killId, Long activityId) {
        return (KillCourse)redisTemplate.opsForHash().get( "activity:" + activityId, killId.toString());
    }

    /**
     * 秒杀业务逻辑流程实现
     * @param dto
     */
    @Override
    public String kill(KillCourseOrderParamDto dto) {
        Date startTime = new Date();
        // JSR303校验非空校验
        Long loginId = 2353L;
        KillCourse killCourse = onlineOne(dto.getKillCourseId(), dto.getActivityId());
        String token = loginId + ":" + dto.getActivityId() + ":" + dto.getKillCourseId();
        // 业务校验:
        // 1.商品是否在活动时间内
        AssertUtil.isTrue(killCourse.isKilling(),"秒杀已经结束啦,请下次再来!");
        // 2.重复秒杀的解决方法：存入redis查询是否存在（用户id,活动id + 商品id唯一键）
        Object isKillOrder = redisTemplate.opsForValue().get(token);
        AssertUtil.isNull(isKillOrder,"请忽重复秒杀!");
        // 使用Redisson信号量扣减库存,防止超卖
        String semaphoreKey = dto.getActivityId() + ":" + dto.getKillCourseId();
        RSemaphore semaphore = redissonClient.getSemaphore(semaphoreKey);
        boolean tryAcquire = semaphore.tryAcquire(CommonConstants.KillCourseState.KILL_COUNT_ONE);
        // 扣减是否成功
        AssertUtil.isTrue(tryAcquire,"商品已经被抢光啦,请下次再来!");


        // 3.扣减成功生成预创订单
        PreKillOrder preOrder = new PreKillOrder();
        preOrder.setOrderNo(CodeGenerateUtils.generateOrderSn(loginId));
        preOrder.setTotalAmount(killCourse.getKillPrice());
        preOrder.setTotalCount((CommonConstants.KillCourseState.KILL_COUNT_ONE));
        preOrder.setUserId(loginId);
        preOrder.setCourseId(killCourse.getCourseId());
        preOrder.setCourseName(killCourse.getCourseName());
        preOrder.setCoursePic(killCourse.getCoursePic());
        preOrder.setActivityId(dto.getActivityId());
        preOrder.setKillCourseId(dto.getKillCourseId());


        // 4.设置用户id,活动id + 商品id唯一键,防止重复秒杀
        redisTemplate.opsForValue().set(
                        token, // 设置防重秒杀
                        preOrder.getOrderNo(),
                        killCourse.getEndTime().getTime(),
                        TimeUnit.MILLISECONDS); // 过期时间为秒杀结束时间


        // 5.将预创订单保存到redis
        redisTemplate.opsForValue().set(
                preOrder.getOrderNo(), // 存入redis预创订单
                preOrder,
                killCourse.getEndTime().getTime(),
                TimeUnit.MILLISECONDS); // 过期时间为秒杀结束时间



        // 6.防止在订单下单页一直不提交订单,库存为其保留一定的时间(3分钟)超过这个时间，库存将会自动释放,删除预存单
        try {
            // 发送MQ延迟消息实现
            Map<String, String> map = new HashMap<>(); // 封装预创单号与信号量Key
            map.put("semaphoreKey",semaphoreKey);
            map.put("orderNo",preOrder.getOrderNo());
            map.put("token",token);
            rocketMQTemplate.syncSend( //
                    "kill-topic:timeout-tags",
                    MessageBuilder.withPayload(JSONObject.toJSONString(map)).build(), // 信号量Key + 预创订单号
                    3000, // 超时时间毫秒
                    2 // 3分钟
            );
        } catch (Exception e) {
            e.printStackTrace();

        }
        long runTime = System.currentTimeMillis() - startTime.getTime();
        log.info("秒杀执行时间：{}",runTime);
        // 7.返回预创订单
        return preOrder.getOrderNo();
    }



    /**
     * 渲染订单页面
     * @param orderNo
     * @return
     */
    @Override
    public Courseinfo2OrderinfoVo oneByOrder(String orderNo) {
        // Jsr303非空校验
        // 从redis获取到预存订单数据
        PreKillOrder preKillOrder = (PreKillOrder) redisTemplate.opsForValue().get(orderNo);
        AssertUtil.isNotNull(preKillOrder,"订单不存在!");
        // 一级封装对象,包含了课程所有信息和总金额数
        Courseinfo2OrderinfoVo Courseinfo2OrderinfoVo = new Courseinfo2OrderinfoVo();
        // 二级封装对象,用于封装课程数据
        List<Course2OrderVo> course2OrderVo = new ArrayList<>();
        // 封装到对象中
        Course course = new Course();
        course.setId(preKillOrder.getCourseId());
        course.setName(preKillOrder.getCourseName());
        course.setPic(preKillOrder.getCoursePic());
        CourseMarket courseMarket = new CourseMarket();
        courseMarket.setPrice(preKillOrder.getTotalAmount());
        course2OrderVo.add(new Course2OrderVo(course,courseMarket));
        // 追加总金额数
        Courseinfo2OrderinfoVo.setTotalAmount(preKillOrder.getTotalAmount());
        // 返回数据
        Courseinfo2OrderinfoVo.setCourseInfos(course2OrderVo);
        return Courseinfo2OrderinfoVo;
    }

    /**
     * 提交订单页面超时取消业务
     * @param map
     */
    @Override
    public void timeoutCancel(Map<String,String> map) {
        String semaphoreKey =  map.get("semaphoreKey");
        String orderNo = map.get("orderNo");
        String token = map.get("token");
        // 获取预创单是否存在
        Boolean isOrderNo = redisTemplate.hasKey(orderNo);
        if (isOrderNo){ // 存在才会归还库存
            // 归还库存,删除预存单
            RSemaphore semaphore = redissonClient.getSemaphore(semaphoreKey);
            semaphore.addPermits(CommonConstants.KillCourseState.KILL_COUNT_ONE);
            // 删除预创单
            Boolean isDelete = redisTemplate.delete(orderNo);
            // 删除防重秒杀
            redisTemplate.delete(token);
            if (isDelete) {
                log.info("秒杀提交订单超时,自动取消!");
            }
            log.info("归还库存成功!");
        }
    }

    /**
     * 根据课程id-活动id查询秒杀课程
     * @param courseId
     * @param activityId
     * @return
     */
    private KillCourse selectByCourseIdAndActivityId(Long courseId, Long activityId) {
        Wrapper<KillCourse> query = new EntityWrapper<>();
        query.eq("course_id",courseId);
        query.eq("activity_id",activityId);
        return selectOne(query);
    }


}
